package com.ds.infra.ibatis.reflection;

import lombok.Data;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 作用： MetaObject是一个元对象，用于封装对象，提供属性的获取和设置功能。
 *
 * @author WeiShaoying
 */
@Data
public class MetaObject {

    // 原始对象，可以是任何类型的对象
    private final Object originalObject;

    // 原始对象的类类型
    private final Class<?> clazz;

    public MetaObject(Object object) {
        this.originalObject = object;
        this.clazz = object != null ? object.getClass() : null;
    }

    public String[] getGetterNames() {
        if (originalObject == null) {
            return new String[0];
        }

        if (originalObject instanceof Map) {
            return ((Map<?, ?>) originalObject).keySet().stream()
                    .map(Object::toString)
                    .toArray(String[]::new);
        }

        // 获取所有public字段和方法
        List<String> names = new ArrayList<>();

        // 添加字段名
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            names.add(field.getName());
        }

        // 添加getter方法对应的属性名
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            String methodName = method.getName();
            if (methodName.startsWith("get") && method.getParameterCount() == 0) {
                String property = methodName.substring(3);
                if (property.length() > 0) {
                    property = Character.toLowerCase(property.charAt(0)) + property.substring(1);
                    if (!names.contains(property)) {
                        names.add(property);
                    }
                }
            }
        }

        return names.toArray(new String[0]);
    }

    public Object getValue(String name) {
        if (originalObject == null) {
            return null;
        }
        // 如果是简单类型，直接返回对象本身
        if (isSimpleType(originalObject.getClass())) {
            return originalObject;
        }
        try {
            if (originalObject instanceof Map) {
                return ((Map<?, ?>) originalObject).get(name);
            }

            // 尝试通过getter方法获取
            String getterMethod = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
            try {
                Method method = clazz.getMethod(getterMethod);
                return method.invoke(originalObject);
            } catch (NoSuchMethodException e) {
                // 如果getter不存在，尝试直接访问字段
                try {
                    Field field = getField(name);
                    if (field != null) {
                        field.setAccessible(true);
                        return field.get(originalObject);
                    }
                } catch (IllegalAccessException ex) {
                    throw new RuntimeException("Error getting field value '" + name + "' from '" + clazz.getName() + "'. Cause: " + ex, ex);
                }
            }

            throw new RuntimeException("No getter or field found for property '" + name + "' in class " + clazz.getName());
        } catch (Exception e) {
            throw new RuntimeException("Error getting value '" + name + "' from '" + clazz.getName() + "'. Cause: " + e, e);
        }
    }

    private boolean isSimpleType(Class<?> clazz) {
        return clazz.isPrimitive() ||
                clazz == String.class ||
                Number.class.isAssignableFrom(clazz) ||
                clazz == Boolean.class ||
                clazz == Character.class;
    }

    private Field getField(String name) {
        Class<?> currentClass = clazz;
        while (currentClass != Object.class) {
            try {
                return currentClass.getDeclaredField(name);
            } catch (NoSuchFieldException e) {
                currentClass = currentClass.getSuperclass();
            }
        }
        return null;
    }

    // 设置属性值
    public void setValue(String name, Object value) {
        try {
            if (originalObject instanceof Map) {
                ((Map) originalObject).put(name, value);
            } else {
                Field field = getField(name);
                if (field != null) {
                    field.setAccessible(true);
                    field.set(originalObject, value);
                }
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Error setting value '" + name + "' of '" + clazz.getName() + "'. Cause: " + e, e);
        }
    }
}
